package org.dftproject.genesis.core.data.flow;

import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import org.dftproject.genesis.core.Activator;
import org.dftproject.genesis.data.genealogy.IPerson;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.IExtension;
import org.eclipse.core.runtime.IExtensionDelta;
import org.eclipse.core.runtime.IExtensionPoint;
import org.eclipse.core.runtime.IExtensionRegistry;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IRegistryChangeEvent;
import org.eclipse.core.runtime.IRegistryChangeListener;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;

/**
 * DataBroker is a singleton class that mediates requests for data between
 * clients and providers.
 */
public class DataBroker {

	/**
	 * The simple name of the data providers extension point.
	 */
	public static final String PROVIDERS_EXTENSION_POINT = "dataProviders";

	private final static DataBroker singleton = new DataBroker();
	private boolean running = false;
	private IRegistryChangeListener registryChangeListener;
	private final Map<String, DataProvider> providerById = Collections.synchronizedMap(new HashMap<String, DataProvider>());

	private DataBroker() {
	}

	/**
	 * Returns the singleton data broker instance.
	 * @return the data broker
	 */
	public static DataBroker getDefault() {
		return singleton;
	}

	/**
	 * Starts the data broker, which starts all registered data providers. As
	 * data providers are dynamically added and removed they will be started or
	 * stopped accordingly.
	 */
	public void start() {
		synchronized (this) {
			if (!running) {
				registryChangeListener = new IRegistryChangeListener() {

					public void registryChanged(IRegistryChangeEvent event) {
						IExtensionDelta[] delta = event.getExtensionDeltas(Activator.ID, PROVIDERS_EXTENSION_POINT);
						for (int i = 0; i < delta.length; i++) {
							IConfigurationElement[] elements;

							switch (delta[i].getKind()) {
							case IExtensionDelta.ADDED:
								elements = delta[i].getExtension().getConfigurationElements();
								for (int j = 0; j < elements.length; j++) {
									try {
										Object provider = elements[j].createExecutableExtension("class");
										if (provider instanceof DataProvider)
											startProvider(elements[j].getAttribute("id"), (DataProvider) provider);
									} catch (CoreException e) {
										e.printStackTrace();
									}
								}
								break;
							case IExtensionDelta.REMOVED:
								elements = delta[i].getExtension().getConfigurationElements();
								for (int j = 0; j < elements.length; j++)
									stopProvider(elements[j].getAttribute("id"));
								break;
							}
						}
					}

				};

				IExtensionRegistry registry = Platform.getExtensionRegistry();
				registry.addRegistryChangeListener(registryChangeListener, Activator.ID);

				IExtensionPoint extensionPoint = registry.getExtensionPoint(Activator.ID, PROVIDERS_EXTENSION_POINT);
				IExtension[] extensions = extensionPoint.getExtensions();
				for (int i = 0; i < extensions.length; i++) {
					IConfigurationElement[] elements = extensions[i].getConfigurationElements();
					for (int j = 0; j < elements.length; j++) {
						try {
							Object provider = elements[j].createExecutableExtension("class");
							if (provider instanceof DataProvider)
								startProvider(elements[j].getAttribute("id"), (DataProvider) provider);
						} catch (CoreException e) {
							e.printStackTrace();
						}
					}
				}

				running = true;
			}
		}
	}

	/**
	 * Stops all data providers.
	 */
	public void stop() {
		// Stop listening to registry changes

		if (registryChangeListener != null) {
			IExtensionRegistry registry = Platform.getExtensionRegistry();
			registry.removeRegistryChangeListener(registryChangeListener);
			registryChangeListener = null;
		}

		providerById.clear();

		running = false;
	}

	protected void startProvider(String id, DataProvider provider) {
		providerById.put(id, provider);
	}

	protected void stopProvider(String id) {
		providerById.remove(id);
	}

	/**
	 * Returns the running instance of the requested data provider.
	 * @param id the ID of the requested data provider
	 * @return the data provider
	 */
	public DataProvider getProvider(String id) {
		return providerById.get(id);
	}

	/**
	 * Collects adaptable objects related to the given URI.
	 * @param uri a URI
	 * @param collector an adaptable object collector
	 */
	public void getRelatedObjects(final String uri, final Collector<Object> collector) {
		// Start the data broker if it is stopped

		if (!running)
			start();

		// Relay the request to each active data provider

		synchronized (providerById) {
			Iterator<DataProvider> iter = providerById.values().iterator();
			while (iter.hasNext()) {
				final DataProvider provider = iter.next();
				Job job = new Job("Get related objects") {

					@Override
					protected IStatus run(IProgressMonitor monitor) {
						provider.getRelatedObjects(uri, collector);
						return Status.OK_STATUS;
					}
					
				};
				job.schedule();
			}
		}
	}

	/**
	 * Collects adaptable objects related to the given URI.
	 * @param uri a URI
	 * @param collector an adaptable object collector
	 */
	public void getRelatedObjects(final IPerson person, final Collector<Object> collector) {
		// Start the data broker if it is stopped

		if (!running)
			start();

		// Relay the request to each active data provider

		synchronized (providerById) {
			Iterator<DataProvider> iter = providerById.values().iterator();
			while (iter.hasNext()) {
				final DataProvider provider = iter.next();
				Job job = new Job("Get related objects") {

					@Override
					protected IStatus run(IProgressMonitor monitor) {
						provider.getRelatedObjects(person, collector);
						return Status.OK_STATUS;
					}
					
				};
				job.schedule();
			}
		}
	}

}
